﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using ControlExtenders;
using Microsoft.WindowsAPICodePack.Taskbar;
using System.Threading;
using Timer = System.Threading.Timer;
using log4net_view.Properties;
using log4net_view.UI;
using log4net_view.core.Settings;
using Microsoft.WindowsAPICodePack.Internal;
using log4net.Core;
using System.Collections;
using log4netCustomAppender;
using log4net_view.core.Log;

namespace log4net_view
{
    public partial class MainForm : Form, ILogMessageNotifiable
    {
        #region 常量
        private const int WM_SIZE = 0x0005;
        private const int SIZE_MINIMIZED = 1;
        #endregion

        #region 字段
        private readonly DockExtender _dockExtender;
        private readonly IFloaty _logDetailsPanelFloaty;
        private readonly bool _isWin7orLater;
        private Timer _taskbarProgressTimer;
        private const int _taskbarProgressTimerPeriod = 2000;
        private LoggerItem _lastHighlightedLogMsgs;
        private bool _ignoreEvents;

        /// <summary>
        /// 是否添加日志
        /// </summary>
        private bool _addedLogMessage;

        /// <summary>
        /// 是否暂停接收日志
        /// </summary>
        private bool _pauseLog;

        /// <summary>
        /// 是否自动滚动至日志末尾行
        /// </summary>
        private bool _autoScrollLog;

        /// <summary>
        /// 日志队列
        /// </summary>
        private readonly Queue<LogMessage> _eventQueue;

        private Timer _logMsgTimer;
        #endregion

        #region 构造函数
        public MainForm()
        {
            this.InitializeComponent();

            this.Minimized += OnMinimized;
            LogManager.Instance.Initialize(new TreeViewLoggerView(loggerTreeView), logListView);

            _dockExtender = new DockExtender(this);

            // Dockable Log Detail View
            _logDetailsPanelFloaty = _dockExtender.Attach(logDetailPanel, logDetailToolStrip, logDetailSplitter);
            _logDetailsPanelFloaty.DontHideHandle = true;
            _logDetailsPanelFloaty.Docking += OnFloatyDocking;

            UserSettings.Load();

            // Windows 7 CodePack (Taskbar icons and progress)
            _isWin7orLater = TaskbarManager.IsPlatformSupported;

            if (_isWin7orLater)
            {
                try
                {
                    // Taskbar Progress
                    TaskbarManager.Instance.ApplicationId = Text;
                    _taskbarProgressTimer = new Timer(OnTaskbarProgressTimer, null, _taskbarProgressTimerPeriod, _taskbarProgressTimerPeriod);
                }
                catch (Exception)
                {
                    // Not running on Win 7?
                    _isWin7orLater = false;
                }
            }

            this.ApplySettings();
            _eventQueue = new Queue<LogMessage>();

            //// Initialize Receivers
            //IReceiver receiver = ReceiverFactory.Instance.Create(typeof(WinDebugReceiver).FullName);
            //this.InitializeReceiver(receiver);

            // Start the timer to process event logs in batch mode
            _logMsgTimer = new Timer(OnLogMessageTimer, null, 1000, 100);
        }

        public MainForm(string title) : this()
        {
            this.Text = title;
        }
        #endregion

        #region private方法
        #region 清除所有日志
        private void ClearAll()
        {
            ClearLogMessages();
            ClearLoggers();
        }

        private void ClearLoggers()
        {
            SetLogMessageDetail(null);
            LogManager.Instance.ClearAll();
        }
        #endregion

        #region 清空日志
        private void ClearLogMessages()
        {
            this.SetLogMessageDetail(null);
            LogManager.Instance.ClearLogMessages();
        }
        #endregion

        #region 设置日志详情
        /// <summary>
        /// 设置日志详情
        /// </summary>
        /// <param name="logMsgItem"></param>
        private void SetLogMessageDetail(LogMessageItem logMsgItem)
        {
            // Store the text to avoid editing without settings the control
            // as readonly... kind of ugly trick...

            if (logMsgItem == null)
            {
                logDetailTextBox.Text = string.Empty;
            }
            else
            {
                StringBuilder sb = new StringBuilder();

                sb.Append(logMsgItem.GetMessageDetails());

                // Append properties
                foreach (KeyValuePair<string, string> kvp in logMsgItem.Message.Properties)
                    sb.AppendFormat("{0} = {1}{2}", kvp.Key, kvp.Value, Environment.NewLine);

                logDetailTextBox.ForeColor = logMsgItem.Message.Level.Color;
                logDetailTextBox.Rtf = sb.ToString();
            }
        }
        #endregion

        #region 定时接收日志
        private void OnLogMessageTimer(object sender)
        {
            LogMessage[] messages;

            lock (_eventQueue)
            {
                // Do a local copy to minimize the lock
                messages = _eventQueue.ToArray();
                _eventQueue.Clear();
            }

            // Process logs if any
            if (messages.Length > 0)
            {
                // InvokeRequired required compares the thread ID of the
                // calling thread to the thread ID of the creating thread.
                // If these threads are different, it returns true.
                if (logListView.InvokeRequired)
                {
                    NotifyLogMsgsCallback d = AddLogMessages;
                    Invoke(d, new object[] { messages });
                }
                else
                {
                    AddLogMessages(messages);
                }
            }
        }
        #endregion

        #region 在列表中显示日志
        /// <summary>
        /// Adds a new log message, synchronously.
        /// </summary>
        private void AddLogMessages(IEnumerable<LogMessage> logMsgs)
        {
            if (_pauseLog)
                return;

            logListView.BeginUpdate();

            foreach (LogMessage msg in logMsgs)
                AddLogMessage(msg);

            logListView.EndUpdate();
        }

        /// <summary>
        /// Adds a new log message, synchronously.
        /// </summary>
        private void AddLogMessage(LogMessage logMsg)
        {
            try
            {
                if (_pauseLog)
                    return;

                RemovedLogMsgsHighlight();

                _addedLogMessage = true;

                LogManager.Instance.ProcessLogMessage(logMsg);
            }
            catch
            {
            }
        }

        private void RemovedLogMsgsHighlight()
        {
            if (_lastHighlightedLogMsgs != null)
            {
                _lastHighlightedLogMsgs.HighlightLogMessages = false;
                _lastHighlightedLogMsgs = null;
            }
        }
        #endregion

        #region 弹出错误消息
        /// <summary>
        /// 弹出错误消息
        /// </summary>
        /// <param name="msg"></param>
        private void ShowErrorBox(string msg)
        {
            MessageBox.Show(this, msg, Text, MessageBoxButtons.OK, MessageBoxIcon.Error);
        }
        #endregion

        #region 放大缩小控件字体
        /// <summary>
        /// 放大缩小控件字体
        /// </summary>
        /// <param name="ctrl"></param>
        /// <param name="zoomIn"></param>
        private void ZoomControlFont(Control ctrl, bool zoomIn)
        {
            // Limit to a minimum size
            float newSize = Math.Max(0.5f, ctrl.Font.SizeInPoints + (zoomIn ? +1 : -1));
            ctrl.Font = new Font(ctrl.Font.FontFamily, newSize);
        }
        #endregion

        #region 还原窗体
        private void RestoreWindow()
        {
            // Make the form visible and activate it. We need to bring the form
            // the front so the user will see it. Otherwise the user would have
            // to find it in the task bar and click on it.

            Visible = true;
            Activate();
            BringToFront();

            if (WindowState == FormWindowState.Minimized)
                WindowState = FormWindowState.Normal;
        }
        #endregion

        #region 应用设置
        private void ApplySettings()
        {
            Opacity = (double)UserSettings.Instance.Transparency / 100;
            ShowInTaskbar = !UserSettings.Instance.HideTaskbarIcon;

            TopMost = UserSettings.Instance.AlwaysOnTop;
            pinOnTopBtn.Checked = UserSettings.Instance.AlwaysOnTop;
            autoLogToggleBtn.Checked = UserSettings.Instance.AutoScrollToLastLog;

            logListView.Font = UserSettings.Instance.LogListFont;
            logDetailTextBox.Font = UserSettings.Instance.LogDetailFont;
            loggerTreeView.Font = UserSettings.Instance.LoggerTreeFont;

            logListView.BackColor = UserSettings.Instance.LogListBackColor;
            logDetailTextBox.BackColor = UserSettings.Instance.LogMessageBackColor;

            LogLevels.Instance.LogLevelInfos[(int)LogLevel.Trace].Color = UserSettings.Instance.TraceLevelColor;
            LogLevels.Instance.LogLevelInfos[(int)LogLevel.Debug].Color = UserSettings.Instance.DebugLevelColor;
            LogLevels.Instance.LogLevelInfos[(int)LogLevel.Info].Color = UserSettings.Instance.InfoLevelColor;
            LogLevels.Instance.LogLevelInfos[(int)LogLevel.Warn].Color = UserSettings.Instance.WarnLevelColor;
            LogLevels.Instance.LogLevelInfos[(int)LogLevel.Error].Color = UserSettings.Instance.ErrorLevelColor;
            LogLevels.Instance.LogLevelInfos[(int)LogLevel.Fatal].Color = UserSettings.Instance.FatalLevelColor;

            //See if the Columns Changed
            bool columnsChanged = false;

            if (logListView.Columns.Count != UserSettings.Instance.ColumnConfiguration.Length)
                columnsChanged = true;
            else
                for (int i = 0; i < UserSettings.Instance.ColumnConfiguration.Length; i++)
                {
                    if (!UserSettings.Instance.ColumnConfiguration[i].Name.Equals(logListView.Columns[i].Text))
                    {
                        columnsChanged = true;
                        break;
                    }
                }

            if (columnsChanged)
            {
                logListView.Columns.Clear();
                foreach (var column in UserSettings.Instance.ColumnConfiguration)
                {
                    ColumnHeader columnHeader = new ColumnHeader();
                    columnHeader.Text = column.Name;
                    switch (column.Field)
                    {
                        case LogMessageField.SequenceNr:
                            columnHeader.Width = 40;
                            break;
                        case LogMessageField.RootLoggerName:
                            columnHeader.Width = 92;
                            break;
                        case LogMessageField.LoggerName:
                            columnHeader.Width = 92;
                            break;
                        case LogMessageField.Level:
                            columnHeader.Width = 48;
                            break;
                        case LogMessageField.Message:
                            columnHeader.Width = 550;
                            break;
                        case LogMessageField.ThreadName:
                            columnHeader.Width = 52;
                            break;
                        case LogMessageField.TimeStamp:
                            columnHeader.Width = 180;
                            break;
                        case LogMessageField.Exception:
                            columnHeader.Width = 300;
                            break;
                        case LogMessageField.CallSiteClass:
                            columnHeader.Width = 100;
                            break;
                        case LogMessageField.CallSiteMethod:
                            columnHeader.Width = 100;
                            break;
                        case LogMessageField.SourceFileName:
                            columnHeader.Width = 200;
                            break;
                        case LogMessageField.SourceFileLineNr:
                            columnHeader.Width = 40;
                            break;
                        case LogMessageField.Properties:
                            columnHeader.Width = 50;
                            break;
                        default:
                            break;
                    };
                    logListView.Columns.Add(columnHeader);
                }
            }
        }
        #endregion
        #endregion

        #region 事件处理
        #region 窗体加载事件
        private void MainForm_Load(object sender, EventArgs e)
        {
            this.versionLabel.Text = @"版本号: v" + AboutForm.AssemblyVersion;
            this.DoubleBuffered = true;
        }
        #endregion

        #region 窗体关闭事件
        private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
        {
            try
            {
                if (_logMsgTimer != null)
                {
                    _logMsgTimer.Dispose();
                    _logMsgTimer = null;
                }

                if (_taskbarProgressTimer != null)
                {
                    _taskbarProgressTimer.Dispose();
                    _taskbarProgressTimer = null;
                }

                UserSettings.Instance.Close();
            }
            catch (Exception)
            {
            }
        }
        #endregion

        #region 窗体最小化事件
        private void OnMinimized(object sender, EventArgs e)
        {
            if (!ShowInTaskbar)
                Visible = false;
        }
        #endregion

        #region 日志详情面板隐藏
        private void OnFloatyDocking(object sender, EventArgs e)
        {
            // make sure the ZOrder remains intact
            this.logListView.BringToFront();
            this.BringToFront();
        }
        #endregion

        #region 更新任务栏进度
        /// <summary>
        /// 更新任务栏进度
        /// </summary>
        /// <param name="o"></param>
        private void OnTaskbarProgressTimer(object o)
        {
            if (_isWin7orLater)
            {
                TaskbarManager.Instance.SetProgressState(_addedLogMessage
                                                                ? TaskbarProgressBarState.Indeterminate
                                                                : TaskbarProgressBarState.NoProgress);
            }
            _addedLogMessage = false;
        }
        #endregion

        #region 暂停
        private void pauseBtn_Click(object sender, EventArgs e)
        {
            _pauseLog = !_pauseLog;

            pauseBtn.Image = _pauseLog ? Resources.Start_32x32 : Resources.Stop_32x32;
            pauseBtn.Checked = _pauseLog;

            if (_isWin7orLater)
            {
                TaskbarManager.Instance.SetOverlayIcon(
                    _pauseLog ? Icon.FromHandle(Resources.Stop_32x32.GetHicon()) : null, String.Empty);
            }
        }
        #endregion

        #region 自动滚动至末尾
        private void autoLogToggleBtn_Click(object sender, EventArgs e)
        {
            this._autoScrollLog = !this._autoScrollLog;

            autoLogToggleBtn.Checked = this._autoScrollLog;
        }
        #endregion

        #region 清除日志
        private void clearBtn_Click(object sender, EventArgs e)
        {
            this.ClearLogMessages();
        }
        #endregion

        #region 清除所有日志
        private void clearAll_Click(object sender, EventArgs e)
        {
            this.ClearAll();
        }
        #endregion

        #region 清除记录器
        private void clearLoggersBtn_Click(object sender, EventArgs e)
        {
            this.ClearLoggers();
        }
        #endregion

        #region 切换日志
        private void logListView_SelectedIndexChanged(object sender, EventArgs e)
        {
            LogMessageItem logMsgItem = null;
            if (logListView.SelectedItems.Count > 0)
                logMsgItem = logListView.SelectedItems[0].Tag as LogMessageItem;

            this.SetLogMessageDetail(logMsgItem);
        }
        #endregion

        #region 跳转至第一条
        private void goToFirstLogBtn_Click(object sender, EventArgs e)
        {
            if (logListView.Items.Count == 0)
                return;

            logListView.Items[0].EnsureVisible();
        }
        #endregion

        #region 跳转至最后一条
        private void goToLastLogBtn_Click(object sender, EventArgs e)
        {
            if (logListView.Items.Count == 0)
                return;

            logListView.Items[logListView.Items.Count - 1].EnsureVisible();
        }
        #endregion

        #region 搜索日志
        private void searchTextBox_KeyUp(object sender, KeyEventArgs e)
        {
            if (e.KeyCode != Keys.Return || e.Alt || e.Control)
                return;
            using (new AutoWaitCursor())
                LogManager.Instance.SearchText(searchTextBox.Text);
        }
        #endregion

        #region 放大缩小控件字体
        private void zoomOutLogListBtn_Click(object sender, EventArgs e)
        {
            this.ZoomControlFont(logListView, false);
        }

        private void zoomInLogListBtn_Click(object sender, EventArgs e)
        {
            this.ZoomControlFont(logListView, true);
        }

        private void zoomOutLogDetailsBtn_Click(object sender, EventArgs e)
        {
            this.ZoomControlFont(logDetailTextBox, false);
        }

        private void zoomInLogDetailsBtn_Click(object sender, EventArgs e)
        {
            this.ZoomControlFont(logDetailTextBox, true);
        }
        #endregion

        #region 设置窗体置顶
        private void pinOnTopBtn_Click(object sender, EventArgs e)
        {
            pinOnTopBtn.Checked = !pinOnTopBtn.Checked;
            this.TopMost = pinOnTopBtn.Checked;
        }
        #endregion

        #region 复制日志内容
        private void copyLogDetailBtn_Click(object sender, EventArgs e)
        {
            if (String.IsNullOrEmpty(logDetailTextBox.Text))
                return;

            Clipboard.SetText(logDetailTextBox.Text);
        }
        #endregion

        #region 还原窗体
        private void appNotifyIcon_MouseDoubleClick(object sender, MouseEventArgs e)
        {
            this.RestoreWindow();
        }
        #endregion

        #region loggerTreeView事件
        private void loggerTreeView_AfterCheck(object sender, TreeViewEventArgs e)
        {
            // If we are suppose to ignore events right now, then just return.
            if (_ignoreEvents)
                return;

            // Set a flag to ignore future events while processing this event. We have
            // to do this because it may be possbile that this event gets fired again
            // during a recursive call. To avoid more processing than necessary, we should
            // set a flag and clear it when we're done.
            _ignoreEvents = true;

            using (new AutoWaitCursor())
            {
                try
                {
                    // Enable/disable the logger item that is represented by the checked node.
                    (e.Node.Tag as LoggerItem).Enabled = e.Node.Checked;
                }
                finally
                {
                    _ignoreEvents = false;
                }
            }
        }

        private void loggerTreeView_AfterSelect(object sender, TreeViewEventArgs e)
        {
            this.RemovedLogMsgsHighlight();
        }

        private void loggerTreeView_NodeMouseDoubleClick(object sender, TreeNodeMouseClickEventArgs e)
        {
            if ((e.Node == null) || ((e.Node.Tag as LoggerItem) == null))
                return;

            if (UserSettings.Instance.HighlightLogMessages)
            {
                _lastHighlightedLogMsgs = e.Node.Tag as LoggerItem;
                _lastHighlightedLogMsgs.HighlightLogMessages = true;
            }
        }
        #endregion
        #endregion

        #region 接收windows消息
        /// <summary>
        /// Catch on minimize event
        /// </summary>
        /// <param name="msg"></param>
        protected override void WndProc(ref Message msg)
        {

            if ((msg.Msg == WM_SIZE)
                && ((int)msg.WParam == SIZE_MINIMIZED)
                && (this.Minimized != null))
            {
                this.Minimized(this, EventArgs.Empty);
            }

            base.WndProc(ref msg);
        }

        protected override void DefWndProc(ref Message m)
        {
            switch (m.Msg)
            {
                case WinMessageHelper.WM_COPYDATA:
                    string str = WinMessageHelper.Receive(ref m);
                    List<LogMessage> logMessages = Newtonsoft.Json.JsonConvert.DeserializeObject<List<LogMessage>>(str);
                    this.Notify(logMessages.ToArray());
                    break;
                default:
                    base.DefWndProc(ref m);
                    break;

            }
        }

        #endregion

        #region ILogMessageNotifiable Members

        /// <summary>
        /// Transforms the notification into an asynchronous call.
        /// The actual method called to add log messages is 'AddLogMessages'.
        /// </summary>
        public void Notify(LogMessage[] logMsgs)
        {
            lock (_eventQueue)
            {
                foreach (var logMessage in logMsgs)
                {
                    _eventQueue.Enqueue(logMessage);
                }
            }
        }

        /// <summary>
        /// Transforms the notification into an asynchronous call.
        /// The actual method called to add a log message is 'AddLogMessage'.
        /// </summary>
        public void Notify(LogMessage logMsg)
        {
            lock (_eventQueue)
            {
                _eventQueue.Enqueue(logMsg);
            }
        }

        #endregion

        #region 自定义事件
        // Specific event handler on minimized action
        public event EventHandler Minimized;

        delegate void NotifyLogMsgCallback(LogMessage logMsg);

        delegate void NotifyLogMsgsCallback(LogMessage[] logMsgs);
        #endregion
    }
}
